package info.demo.filter;


import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class CryptoServletRequestWrapper extends HttpServletRequestWrapper {
    // parâmetros do wrapper
    private Map<String, String[]> parameters;

    private String encryptedParamName;

    public CryptoServletRequestWrapper(HttpServletRequest httpServletRequest, String encryptedParamName) {
        super(httpServletRequest);
        if (encryptedParamName == null)
            throw new IllegalArgumentException("Encrypted parameter name cannot be null");
        this.encryptedParamName = encryptedParamName;
        this.parameters = new HashMap<String, String[]>();
        String encryptedParam = super.getParameter(encryptedParamName);
        if (encryptedParam != null)
            this.setParameters(Crypto.decrypt(encryptedParam));
    }

    /**
     * Recupera um par&acirc;metro do wrapper
     *
     * @param parameter nome do par&acirc;metro
     */
    public String getParameter(String parameter) {
        // descartar o parâmetro criptografado do request original
        if (encryptedParamName.equals(parameter))
            return null;
        String value = super.getParameter(parameter);
        if (value == null) {
            String[] values = parameters.get(parameter);
            value = values != null && values.length > 0 ? values[0] : null;
        }
        return value;
    }

    /**
     * Recupera um mapa dos par&acirc;metros do wrapper <b>(chave=valor)</b>
     *
     * @return Map mapa dos par&acirc;metros
     */
    @SuppressWarnings({"unchecked"})
    public Map getParameterMap() {
        Map<String, String[]> map = new HashMap<String, String[]>(super.getParameterMap());
        // descartar o parâmetro criptografado do request original
        map.remove(encryptedParamName);
        for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
            if (map.containsKey(entry.getKey())) {
                String[] originalValues = map.get(entry.getKey());
                int originalValuesLength = originalValues != null ? originalValues.length : 0;
                int wrapperValuesLength = entry.getValue() != null ? entry.getValue().length : 0;
                String[] values = new String[originalValuesLength + wrapperValuesLength];
                if (originalValuesLength > 0)
                    System.arraycopy(originalValues, 0, values, 0, originalValuesLength);
                if (wrapperValuesLength > 0)
                    System.arraycopy(entry.getValue(), 0, values, originalValuesLength, wrapperValuesLength);
                map.put(entry.getKey(), values);
            } else {
                map.put(entry.getKey(), entry.getValue());
            }
        }
        return Collections.unmodifiableMap(map);
    }

    /**
     * Recupera uma enumera&ccedil;&atilde;o das chaves dos par&acirc;metros
     *
     * @return Enumeration com as chaves dos par&acirc;metros
     */
    @SuppressWarnings({"unchecked"})
    public Enumeration getParameterNames() {
        return Collections.enumeration(this.getParameterMap().keySet());
    }

    /**
     * Recupera uma lista de valores de um par&acirc;metro
     *
     * @return String[] lista de valores de um par&acirc;metro
     */
    @SuppressWarnings({"unchecked"})
    public String[] getParameterValues(String parameter) {
        // descartar o parâmetro criptografado do request original
        if (encryptedParamName.equals(parameter))
            return null;
        String[] originalValues = super.getParameterValues(parameter);
        String[] wrapperValues = parameters.get(parameter);
        int originalValuesLength = originalValues != null ? originalValues.length : 0;
        int wrapperValuesLength = wrapperValues != null ? wrapperValues.length : 0;
        String[] parameterValues = new String[originalValuesLength + wrapperValuesLength];
        if (originalValuesLength > 0)
            System.arraycopy(originalValues, 0, parameterValues, 0, originalValuesLength);
        if (wrapperValuesLength > 0)
            System.arraycopy(wrapperValues, 0, parameterValues, originalValuesLength, wrapperValuesLength);
        return parameterValues.length > 0 ? parameterValues : null;
    }

    /**
     * Configura um novo par&acirc;metro no wrapper
     *
     * @param parameter nome do par&acirc;metro
     * @param value     valor do par&acirc;metro
     */
    public void setParameter(String parameter, String value) {
        String[] values = parameters.get(parameter);
        if (values == null)
            values = new String[0];
        String[] newValues = new String[values.length + 1];
        System.arraycopy(values, 0, newValues, 0, values.length);
        newValues[values.length] = value;
        parameters.put(parameter, newValues);
    }

    /**
     * Configura os par&acirc;metros da queryString no wrapper <b>(chave=valor)</b>
     *
     * @param queryString query string da URL
     * @see #parameters
     */
    public void setParameters(String queryString) {
        String[] parameters = queryString.split("&");
        for (String parameter : parameters) {
            // pesquisar a chave do parâmetro
            int paramDivisor = parameter.indexOf("=");
            if (paramDivisor > -1) {
                // recuperar a chave do parâmetro
                String paramName = parameter.substring(0, paramDivisor);
                // verificar se existe valor para o parâmetro
                if ((paramDivisor + 1) < parameter.length()) {
                    // recuperar o valor do parâmetro
                    String paramValue;
                    try {
                        String encoding = super.getCharacterEncoding() != null ? super.getCharacterEncoding() : "UTF-8";
                        paramValue = URLDecoder.decode(parameter.substring((paramDivisor + 1)), encoding);
                    } catch (UnsupportedEncodingException e) {
                        paramValue = parameter.substring((paramDivisor + 1));
                    }
                    // adicionar novamente o parâmetro e o valor à requisição
                    this.setParameter(paramName, paramValue);
                } else {
                    // adicionar novamente o parâmetro sem valor à requisição
                    this.setParameter(paramName, null);
                }
            } else {
                // adicionar novamente o parâmetro à requisição
                this.setParameter(parameter, null);
            }
        }
    }

}
